// new Vue 的时候传入一个对象，这个对象会告诉我们要挂载的元素和数据
class Vue {
  constructor(obj_instace){
    this.$data = obj_instace.data
    // 初始化时调用数据监听函数Observer
    Observer(this.$data)
    Compile(obj_instace.el,this)
  }
}


// 数据劫持 -- 监听实例里的数据
function Observer(data_instace){
  // 递归出口
  if(!data_instace || typeof data_instace !== 'object') return
  const dependency = new Dependency() // 创建实例到get中操作
  console.log(Object.keys(data_instace))
  // 对data中的每一个数据进行监听
  Object.keys(data_instace).forEach( key => {
    // 这个方法可以修改对象的现有属性
    // Object.defineProperty('要操作的对象','要操作的属性',{在对象里实现监听}) 
    // 在改变值之前把值复制一遍
    let value = data_instace[key]
    Observer(value) // 递归 - 数据劫持
    Object.defineProperty(data_instace,key,{
      enumerable:true, // 属性可以枚举
      configurable:true, // 属性描述符可以被改变
      get(){ // 属性值被读取时触发
        // 订阅者加入依赖实例的数组
        // console.log(Dependency.temp,'Dependency.temp---');
        Dependency.temp && dependency.addSub(Dependency.temp)
        if(Dependency.temp){
          console.log(Dependency.temp,'Dependency.temp');
        }
        console.log(`访问啦属性：${key} => 值：${value}`);
        return value
      },
      set(newValue){ // 属性值被改变时触发
        console.log(`属性：${key} 的值修改为：${newValue}`);
        value = newValue
        Observer(newValue) // 防止传入的新值是对象
        // 如果给$data添加新的属性，并且他的值是对象，新对象身上也没有get和set方法
        dependency.notify()//修改
      }
    })
  })
}


// 我们拿到数据后要渲染到页面？
// 获取页面元素
// 放入临时内存区域 // 目的是减少dom操作
// 应用Vue数据
// 渲染页面



// HTML模板解析 - 替换DOM内
// Compile('Vue实例中挂载的元素','Vue实例')
function Compile(element,vm){
  // $el 关联dom元素
  vm.$el = document.querySelector(element)
  // 创建文档碎片，临时放dom元素
  const fragment = document.createDocumentFragment();
  console.log(vm.$el.childNodes,'childNodes');
  let child;
  while(child = vm.$el.firstChild){ //循环
    fragment.append(child)
  }
  console.log(fragment,'fragment');
  console.log(fragment.childNodes,'fragment.childNodes');


  // 创建函数修改fragment里边的内容
  // 只需要修改文本节点
  fragment_Compile(fragment)
  function fragment_Compile(node){
    // 创建匹配的样式变量，不需要修改每个节点的内容，最简单的匹配就是用正则表达式
    const pattern = /\{\{\s*(\S+)\s*\}\}/
    if(node.nodeType === 3){ // 节点类型为3代表文本节点
      const xxx = node.nodeValue
      console.log(node,'node');
      console.log(node.nodeValue,'node.nodeValue');
      // exec()检索正则表达式的匹配，返回一个数组，返回匹配成功的结果
      const result_regex = pattern.exec(node.nodeValue)
      if(result_regex){
        console.log(result_regex);
        // 匹配返回的结果是个数组，数组中有多个结果，其中索引为1的元素才是我们需要的
        const arr = result_regex[1].split('.');
        // 链式获取属性里的子属性里的值需要用到reduce()
        const value = arr.reduce(
          (total,current)=>total[current],vm.$data
        )
        // 当前节点值替换
        // replace('替换的内容','被替换成的啥')
        node.nodeValue = xxx.replace(pattern,value)
        console.log(value,'value');

        // 创建订阅者将来更新
        new Watcher(vm,result_regex[1],newValue =>{//需要新的属性值来替换
        //   // 替换之前要保存要替换的内容，不保存将替换的是插值表达式替换过的内容，而不是插值表达式
          node.nodeValue = xxx.replace(pattern,newValue)
        })
      }
      return // 递归出口
    } 
    // 去找v-model
    if(node.nodeType === 1 && node.nodeName ==='INPUT'){
      const attr = Array.from(node.attributes); // 获取元素属性
      console.log(attr,'attr');
      attr.forEach(i=>{
        if(i.nodeName === 'v-model'){
          console.log(i.nodeValue);
          const value = i.nodeValue.split('.').reduce(
            (total,current)=> total[current],vm.$data
          )
          console.log(value);
          node.value = value
          new Watcher(vm,i.nodeValue,newValue => {
            node.value = newValue
          })
          node.addEventListener('input',e =>{
            // 错误示范 vm.data.[more.like] 赋值失败
            // const name = i.newValue
            // console.log(name);
            // vm.$data[name] = e.target.value

            // ['more','like']
            const arr1 = i.nodeValue.split('.')
            // ['more]
            const arr2 = arr1.slice(0,arr1.length-1)
            // vm.$data.more
            const final = arr2.reduce(
              (total,current) => total[current],vm.$data
            )
            // vm.$data.more['like'] = e.target.value
            final[arr1[arr1.length-1]] = e.target.value
          })
        }
      })
    }
    // 节点类型为3就替换，不为3就继续循环
    node.childNodes.forEach(child=>fragment_Compile(child));
  }
  // 将文档碎片应用到页面
  vm.$el.appendChild(fragment)
}


// 依赖 - 收集和通知订阅者（发布者）
class Dependency{
  constructor(){
    this.subscribers = [] //存放订阅者信息
  }
  addSub(sub){
    this.subscribers.push(sub)
  }
  notify(){//通知订阅者的方法
    this.subscribers.forEach(sub=>sub.updata())
  }
}

// 订阅者
class Watcher{ // 去更新属性
  constructor(vm,key,callback){
    this.vm = vm // vue实例
    this.key = key // vue实例属性
    this.callback = callback // 记录如何更新文本内容
    // 临时属性 - 触发getter
    Dependency.temp = this // 订阅者
    console.log(`用属性${key}创建订阅者`);
    key.split('.').reduce((total,current)=> total[current],vm.$data )
    // key.split('.').reduce((total,current)=> {total[current],vm.$data} )
    Dependency.temp = null
  } 
  updata(){
    // 实际是实例在调用
    const value = this.key.split('.').reduce(
      (total,current)=> total[current],this.vm.$data
    )
    this.callback(value)
  }
}


// 此时单向数据绑定就实现啦
// 接下来需要判断哪里用啦v-model然后去改值



